﻿using System;
using System.Linq;
using System.Text;
using DevExpress.Xpo;
using DevExpress.ExpressApp;
using System.ComponentModel;
using DevExpress.ExpressApp.DC;
using DevExpress.Data.Filtering;
using DevExpress.Persistent.Base;
using System.Collections.Generic;
using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;

namespace aztall.Module.BusinessObjects
{
	public enum MetricScope
	{
		Promotion,    /* metric value apply to a promo attribute */
		Customer,     /* metric value apply to a specific customer */
		Sku,          /* metric value apply to a specific sku */
		CustomerSku,  /* metric value apply to a spcific customer + sku */
		Derived				/* metric value is calcuated from other metrics */
	}

	[DefaultClassOptions, XafDefaultProperty("DisplayText"), NavigationItem("Administration")]
	[ImageName("Action_Totals_Row")]
	public class Metric : RootObject
	{
		#region protected fields
		protected string _MetricCode;
		protected string _MetricDesc;
		protected string _MetricType;
		protected MetricScope _MetricScope;
		#endregion
		public Metric(Session sesion) : base(sesion)
		{
		}
		[RuleRequiredField(DefaultContexts.Save, SkipNullOrEmptyValues = false)]
		[VisibleInLookupListView(false), Index(1)]
		public virtual string MetricType
		{
			get { return _MetricType; }
			set { SetPropertyValue(nameof(MetricType), ref _MetricType, value); }
		}
		public virtual MetricScope MetricScope
		{
			get { return _MetricScope; }
			set { SetPropertyValue(nameof(MetricScope), ref _MetricScope, value); }

		}
		[RuleRequiredField(DefaultContexts.Save, SkipNullOrEmptyValues = false), Size(10), Index(2)]
		[RuleUniqueValue(DefaultContexts.Save, CriteriaEvaluationBehavior = CriteriaEvaluationBehavior.InTransaction)]
		public virtual string MetricCode
		{
			get
			{
				if (String.IsNullOrWhiteSpace(_MetricCode))
					_MetricCode = this.GetType().Name.Substring(6);    //return XXXX in "MetricXXXX"
				return _MetricCode;
			}
			set { SetPropertyValue(nameof(MetricCode), ref _MetricCode, value); }
		}
		[Size(256), Index(3)]
		public virtual string MetricDesc
		{
			get { return _MetricDesc; }
			set { SetPropertyValue(nameof(MetricDesc), ref _MetricDesc, value); }
		}
		[VisibleInListView(false), VisibleInDetailView(false)]
		public string DisplayText
		{
			get { return string.Format("{0}: {1}", MetricCode, MetricDesc); }
		}
		public override string ToString()
		{
			return DisplayText;
		}
		public override int GetHashCode()
		{
			if (MetricCode == null)
				return "".GetHashCode();
			else
				return MetricCode.GetHashCode();
		}
		public static Metric GetObjectByMetricCode(Session session, string metricCode)
		{
			return session.GetObjectByKey<Metric>(MetricHelper.GetOid(metricCode));
		}
		public virtual double TotalValue(Promo promo, Customer cust, Sku sku, double rate, IList<CustomerSkuDW> DWs = null)
		{
			double result = 0.0;
			switch (this.MetricCode)
			{
				case "1010":    //Fix amount Per Promotion, Value = Rate$
					result = rate;
					break;
				case "1011":    //Fix amount Per Promotion, Value = Rate$
					result = promo?.Customers?.Count() * rate ?? 0;
					break;
				case "1012":    //Fix amount Per the Customer, Rate$
					result = (cust == null) ? 0 : rate;
					break;
				case "1013":    //Fix amount Per Each Sku, Rate$ * Number of Promotion Skus
					result = (promo == null) ? 0 : rate * promo.Skus.Count;
					break;
				case "1014":    //Fix amount Per the Sku, Rate$
					result = (sku == null) ? 0 : rate;
					break;
				case "1015":    //Fix amount Per Promotion Day, Rate$ * Promotion Days
					result = promo?.PromoDays * rate ?? 0;
					break;
				case "2011":    //Per Promotion Volume, Rate$ * (Uplift)
					if (promo != null && cust != null && sku != null)
						result = rate * promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.Uplift * c.Days);
					break;
				case "2012":    //Per Promotion Volume, Rate$ * (CANN)
					if (promo != null && cust != null && sku != null)
						result = rate * promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.CANN * c.Days);
					break;
				case "2013":    //Per Promotion Volume, Rate$ * (PPD)
					if (promo != null && cust != null && sku != null)
						result = rate * promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.PPD * c.Days);
					break;
				case "2014":    //Per Actual Volume, Rate$ * Actual Volume during Promotion
					if (promo != null && cust != null && sku != null)
						result = rate * DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Actual }, cust.Oid, sku.Oid).Sum(c => c.Value);
					break;
				case "2015":    //Per Base Volume, Rate$ * Base Volume during Promotion
					if (promo != null && cust != null && sku != null)
						result = rate * DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Base }, cust.Oid, sku.Oid).Sum(c => c.Value);
					break;
				case "2050":    //Per Promotion Volume, Rate$ * (Uplift + PPD + Cann)
					if (promo != null && cust != null && sku != null)
						result = rate * promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => (c.Uplift + c.PPD + c.CANN) * c.Days);
					break;
				case "2051":    //Per Projection Volume, Rate$ * (Base Volume during Promotion + Uplift + PPD + Cann)
					if (promo != null && cust != null && sku != null)
					{
						var BaseVol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Base }, cust.Oid, sku.Oid).Sum(c => c.Value);
						result = BaseVol * promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => (c.Uplift + c.PPD + c.CANN) * c.Days);
					}
					break;
				case "3011":    //Percentage Per Actual Volume, (Rate% / 100) * (Unit Price at Promo Start Date) * (Promotion Uplift)
					if (promo != null && cust != null && sku != null)
					{
						var vol = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.Uplift * c.Days);
						var CustomerSkuPrice = PriceList.GetCustomerSkuPrice(promo.Session, promo.StartDate, promo.StartDate, cust.Oid, sku.Oid).FirstOrDefault();
						result = rate / 100 * (CustomerSkuPrice?.UnitPrice ?? 0) * vol;
					}
					break;
				case "3012":    //Percentage Per Actual Volume, (Rate% / 100) * (Unit Price at Promo Start Date) * (Promotion CANN)
					if (promo != null && cust != null && sku != null)
					{
						var vol = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.CANN * c.Days);
						var CustomerSkuPrice = PriceList.GetCustomerSkuPrice(promo.Session, promo.StartDate, promo.StartDate, cust.Oid, sku.Oid).FirstOrDefault();
						result = rate / 100 * (CustomerSkuPrice?.UnitPrice ?? 0) * vol;
					}
					break;
				case "3013":    //Percentage Per Actual Volume, (Rate% / 100) * (Unit Price at Promo Start Date) * (Promotion PPD)
					if (promo != null && cust != null && sku != null)
					{
						var vol = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.PPD * c.Days);
						var CustomerSkuPrice = PriceList.GetCustomerSkuPrice(promo.Session, promo.StartDate, promo.StartDate, cust.Oid, sku.Oid).FirstOrDefault();
						result = rate / 100 * (CustomerSkuPrice?.UnitPrice ?? 0) * vol;
					}
					break;
				case "3014":    //Percentage Per Actual Volume, (Rate% / 100) * (Unit Price at Promo Start Date) * (Actual Volume during Promotion)
					if (promo != null && cust != null && sku != null)
					{
						var CustomerSkuPrice = PriceList.GetCustomerSkuPrice(promo.Session, promo.StartDate, promo.StartDate, cust.Oid, sku.Oid).FirstOrDefault();
						double ActualVol;
						if (DWs == null)
							ActualVol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Actual }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							ActualVol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.Actual) && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);

						result = rate / 100 * (CustomerSkuPrice?.UnitPrice ?? 0) * ActualVol;
					}
					break;
				case "3015":    //Percentage Per Base Volume, (Rate% / 100) * (Unit Price at Promo Start Date) * (Base Volume during Promtion)
					if (promo != null && cust != null && sku != null)
					{
						double BaseVol;
						var CustomerSkuPrice = PriceList.GetCustomerSkuPrice(promo.Session, promo.StartDate, promo.StartDate, cust.Oid, sku.Oid).FirstOrDefault();
						if (DWs == null)
							BaseVol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Base }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							BaseVol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.Base) && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);
						result = rate / 100 * (CustomerSkuPrice?.UnitPrice ?? 0) * BaseVol;
					}
					break;
				case "3050":    //Percentage Per Promotion Volume, (Rate% / 100) * (Unit Price at Promo Start Date) * (Uplift + PPD + Cann)
					if (promo != null && cust != null && sku != null)
					{
						var CustomerSkuPrice = PriceList.GetCustomerSkuPrice(promo.Session, promo.StartDate, promo.StartDate, cust.Oid, sku.Oid).FirstOrDefault();
						var PromoVol = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => (c.Uplift + c.PPD + c.CANN) * c.Days);
						result = rate / 100 * (CustomerSkuPrice?.UnitPrice ?? 0) * PromoVol;
					}
					break;
				case "3051":    //Percentage Per Projection Volume, (Rate% / 100) * (Unit Price at Promo Start Date) * (Base Volume during Promotion + Uplift + PPD + Cann)
					if (promo != null && cust != null && sku != null)
					{
						var CustomerSkuPrice = PriceList.GetCustomerSkuPrice(promo.Session, promo.StartDate, promo.StartDate, cust.Oid, sku.Oid).FirstOrDefault();
						var PromoVol = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => (c.Uplift + c.PPD + c.CANN) * c.Days);
						double BaseVol;
						if (DWs == null)
							BaseVol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Base }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							BaseVol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.Base) && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);
						result = rate / 100 * (CustomerSkuPrice?.UnitPrice ?? 0) * (PromoVol + BaseVol);
					}
					break;
				case "4011":    //Per Retail Base Volume (in retail UOM), Rate$ * Retail Base Volume during Promotion
					if (promo != null && cust != null && sku != null)
					{
						double RetailBaseVol;
						if (DWs == null)
							RetailBaseVol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.RetailBaseVol }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							RetailBaseVol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailBaseVol) && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);

						result = rate * RetailBaseVol;
					}
					break;
				case "4012":    //Per Retail Actual Volume (in retail UOM), Rate$ * Retail Actual Volume during Promotion
					if (promo != null && cust != null && sku != null)
					{
						double RetailActualVol;
						if (DWs == null)
							RetailActualVol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.RetailActualVol }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							RetailActualVol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailActualVol) && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);

						result = rate * RetailActualVol;
					}
					break;
				case "4050":    //Per Retail Promo Volume (in retail UOM), Rate$ * (Retail Actual Volume - Retail Base Volume)
					if (promo != null && cust != null && sku != null)
					{
						double Vol;
						if (DWs == null)
							Vol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate,
																new string[] { VolumeMetricCode.RetailBaseVol, VolumeMetricCode.RetailActualVol }, cust.Oid, sku.Oid)
																.Sum(c => c.Value * (c.MetricCode == VolumeMetricCode.RetailActualVol ? 1 : -1));
						else
							Vol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailBaseVol || c.MetricCode == VolumeMetricCode.RetailActualVol)
										&& (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value * (c.MetricCode == VolumeMetricCode.RetailActualVol ? 1 : -1));

						result = rate * Vol;
					}
					break;
				case "5010":    //Percent Per Retail Base Volume (in retail UOM), (Rate% / 100) * (Retail Unit Price ) * Retail Base Volume during Promotion
					if (promo != null && cust != null && sku != null)
					{
						double Vol;
						if (DWs == null)
							Vol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate,
												new string[] { VolumeMetricCode.RetailBaseVol }, cust.Oid, sku.Oid)
												.Sum(c => c.Value);
						else
							Vol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailBaseVol) && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);

						result = rate / 100 * sku.RetailPrice * Vol;
					}
					break;
				case "5011":    //Percent Per Retail Actual Volume (in retail UOM), (Rate% / 100) * Retail Unit Price * Retail Actual Volume during Promotion
					if (promo != null && cust != null && sku != null)
					{
						double Vol;
						if (DWs == null)
							Vol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate,
												new string[] { VolumeMetricCode.RetailActualVol }, cust.Oid, sku.Oid)
												.Sum(c => c.Value);
						else
							Vol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailActualVol) && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);

						result = rate / 100 * sku.RetailPrice * Vol;
					}
					break;
				case "5050":    //Percent Per Retail Promo Volume (in retail UOM), (Rate% / 100) * Retail Unit Price  * (Retail Actual Volume - Retail Base Volume) during Promotion
					if (promo != null && cust != null && sku != null)
					{
						double Vol;
						if (DWs == null)
							Vol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate,
												new string[] { VolumeMetricCode.RetailActualVol, VolumeMetricCode.RetailBaseVol }, cust.Oid, sku.Oid)
												.Sum(c => c.Value * (c.MetricCode == VolumeMetricCode.RetailActualVol ? 1 : -1));
						else
							Vol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailActualVol || c.MetricCode == VolumeMetricCode.RetailBaseVol) 
										&& (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value * (c.MetricCode == VolumeMetricCode.RetailActualVol ? 1 : -1));

						result = rate / 100 * sku.RetailPrice * Vol;
					}
					break;
				case "9010":    //Uplift Volume
					if (promo != null && cust != null && sku != null)
					{
						result = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.Uplift);
					}
					break;
				case "9011":    //Cannibalized Volume (Cann)
					if (promo != null && cust != null && sku != null)
					{
						result = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.CANN);
					}
					break;
				case "9012":    //Post Promotion Dip Volume (PPD)
					if (promo != null && cust != null && sku != null)
					{
						result = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.PPD);
					}
					break;
				case "9013":    //Base Volume
					if (promo != null && cust != null && sku != null)
					{
						result = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Base }, cust.Oid, sku.Oid).Sum(c => c.Value);
					}
					break;
				case "9014":    //Actual Volume
					if (promo != null && cust != null && sku != null)
					{
						if (DWs == null)
							result = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Actual }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							result = DWs.Where(c => (c.MetricCode == VolumeMetricCode.Actual)
												&& (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);
					}
					break;
				case "9050":    //Promotion Volume, Uplift + PPD + Cann
					if (promo != null && cust != null && sku != null)
					{
						result = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.Uplift + c.CANN + c.PPD);
					}
					break;
				case "9051":    //Projection Volume: Total BaseVol during Promotion  + Uplift + PPD + Cann
					if (promo != null && cust != null && sku != null)
					{
						var PromoVol = promo.PromoVols.Where(c => c.Customer == cust && c.Sku == sku).Sum(c => c.Uplift + c.CANN + c.PPD);
						double BaseVol;
						if (DWs == null)
							BaseVol = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.Base }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							BaseVol = DWs.Where(c => (c.MetricCode == VolumeMetricCode.Base)
												&& (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);

						result = PromoVol + BaseVol;
					}
					break;
				case "9120":    //Retail Base Volume
					if (promo != null && cust != null && sku != null)
					{
						result = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.RetailBaseVol }, cust.Oid, sku.Oid).Sum(c => c.Value);
					}
					break;
				case "9121":    //Retail Actual Volume
					if (promo != null && cust != null && sku != null)
					{
						if (DWs == null)
							result = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.RetailActualVol }, cust.Oid, sku.Oid).Sum(c => c.Value);
						else
							result = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailActualVol)
											 && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value);
					}
					break;
				case "9151":    //Retail Promotion Volume: Retail Actual Volume - Retail Actual Base Volumn during Promotion
					if (promo != null && cust != null && sku != null)
					{
						if (DWs == null)
							result = DW.GetObjects(promo.Session, promo.StartDate, promo.EndDate, new string[] { VolumeMetricCode.RetailActualVol, VolumeMetricCode.RetailActualVol }, cust.Oid, sku.Oid)
												.Sum(c => c.Value * (c.MetricCode == VolumeMetricCode.RetailActualVol ? 1 : -1));
						else
							result = DWs.Where(c => (c.MetricCode == VolumeMetricCode.RetailActualVol || c.MetricCode == VolumeMetricCode.RetailActualVol)
											 && (c.CustomerOid == cust.Oid) && (c.SkuOid == sku.Oid)).Sum(c => c.Value * (c.MetricCode == VolumeMetricCode.RetailActualVol ? 1 : -1));
					}
					break;
				default: throw new Exception("Operation not support in Metric" + nameof(MetricCode));
			}
			return result;
		}
	}
}